/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.excel.write;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.star.framework.core.dict.EnumEntry;
import cn.star.framework.excel.annotation.Convert;
import cn.star.framework.excel.config.ReadWriteConstant;
import cn.star.framework.excel.core.Column;
import cn.star.framework.excel.core.Type;
import cn.star.framework.excel.enhance.GlobalConverterRegister;
import cn.star.framework.excel.handler.CellDropdownHandler;
import cn.star.framework.excel.handler.CellHeaderCommentHandler;
import cn.star.framework.excel.handler.CellLinkHandler;
import cn.star.framework.excel.handler.CellStyleHandler;
import cn.star.framework.excel.handler.SheetFreezePaneHandler;
import cn.star.framework.excel.handler.SheetWaterMarkHandler;
import cn.star.framework.excel.handler.settings.FreePane;
import cn.star.framework.excel.handler.settings.HeaderComment.Comment;
import cn.star.framework.excel.handler.settings.Link;
import cn.star.framework.excel.handler.settings.WaterMark;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.executor.AbstractExcelWriteExecutor;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;

/**
 * 导出数据<br>
 * 支持将数据导出到一个Sheet中<br>
 * {@link AbstractExcelWriteExecutor#doConvert(CellWriteHandlerContext)}<br>
 *
 * @author zhaoweiping
 *     <p style='color: red'>Created on 2024-09-23 11:06:00
 * @since 3.0.0
 */
@Slf4j
public class WriteContext {

  /** Sheet名称 */
  protected String name;

  /** 数据结构 */
  protected List<Column> columns;

  protected ExcelWriterSheetBuilder builder;

  /** 判处不需要导出的字段 */
  protected Set<String> excludeColumns = new HashSet<>();

  protected Map<Integer, List<String>> dropdownValues = new HashMap<>();

  protected WriteContext() {}

  public WriteContext(String name, List<Column> columns) {
    this.name = name;
    this.columns = columns;
    this.initExcelWriterSheetBuilder();
  }

  public List<Column> getIncludeColumns() {
    return CollectionUtil.sort(columns, Comparator.comparingInt(Column::getOrder)).stream()
        .filter(i -> !i.isIgnore() && !excludeColumns.contains(i.getKey()))
        .collect(Collectors.toList());
  }

  protected List<Column> getExcludeColumns() {
    return columns.stream()
        .filter(i -> i.isIgnore() || excludeColumns.contains(i.getKey()))
        .collect(Collectors.toList());
  }

  protected void initExcelWriterSheetBuilder() {
    builder = EasyExcel.writerSheet(name);
  }

  public WriteContext registerConverter(Converter<?>... converters) {
    if (ArrayUtil.isNotEmpty(converters)) {
      for (Converter<?> converter : converters) {
        builder.registerConverter(converter);
      }
    }

    return this;
  }

  public WriteContext registerWriteHandler(WriteHandler... handlers) {
    if (ArrayUtil.isNotEmpty(handlers)) {
      for (WriteHandler handler : handlers) {
        builder.registerWriteHandler(handler);
      }
    }

    return this;
  }

  public WriteContext addExcludeColumns(String... names) {
    if (ArrayUtil.isNotEmpty(names)) {
      excludeColumns.addAll(CollectionUtil.toList(names));
    }

    return this;
  }

  public Map<Integer, Column> getHeaderMap() {
    List<Column> includeColumns = this.getIncludeColumns();
    Map<Integer, Column> headerMap = new HashMap<>(includeColumns.size());
    for (int i = 0; i < includeColumns.size(); i++) {
      Column column = includeColumns.get(i);
      headerMap.put(i, column);
      GlobalConverterRegister.register(Type.WRITE, column.getConverters());
    }

    return headerMap;
  }

  public WriteSheet builder() {
    List<List<String>> head =
        this.getHeaderMap().values().stream()
            .map(column -> Collections.singletonList(column.getFirstName()))
            .collect(Collectors.toList());
    List<Column> excludeColumns = this.getExcludeColumns();
    builder.needHead(true).head(head);
    builder.excludeColumnFieldNames(
        excludeColumns.stream().map(Column::getKey).collect(Collectors.toList()));

    // 自动列宽
    this.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy());
    // 设置下拉选择框&校验
    this.setCellDropdownValues();
    // 设置头部列的批注信息
    this.setCellHeaderComments();
    // 设置水印
    this.setSheetWaterMark(null);
    // 添加链接
    this.setCellLink(null);
    // 全局默认样式 <pre>this.registerWriteHandler(CellStyleHandler.getDefaultCellStyleStrategy());</pre>
    this.setCellStyle();
    // 冻结窗格
    this.setFreezePaneHandler(this.getDefaultFreezePaneSetting());

    return builder.build();
  }

  protected FreePane getDefaultFreezePaneSetting() {
    return new FreePane();
  }

  /**
   * 动态设置下拉框候选列表数据<br>
   * 此处设置的为动态数据，需要和{@link Convert#value()}进行配合使用，否则导入导出会出现数据不一致的异常
   *
   * @param key {@link Column#getKey()}
   * @param values 下拉候选数据列表
   */
  public void addDropdownValues(String key, List<String> values) {
    Map<Integer, Column> headerMap = this.getHeaderMap();
    List<Integer> keys = new ArrayList<>(headerMap.keySet());
    for (int index : keys) {
      Column column = headerMap.get(index);
      if (key.equals(column.getKey())) {
        this.dropdownValues.put(index, values);
        return;
      }
    }
  }

  @SuppressWarnings("unchecked")
  protected void setCellDropdownValues() {
    Map<Integer, Column> headerMap = this.getHeaderMap();
    Map<Boolean, List<String>> booleanValues = ReadWriteConstant.getBooleanValues();
    List<Integer> keys = new ArrayList<>(headerMap.keySet());
    for (int index : keys) {
      Column column = headerMap.get(index);
      Class<?> type = column.getType();
      if (type == null || dropdownValues.get(index) != null) {
        continue;
      }
      // 设置默认下拉数据列表
      if (EnumEntry.class.isAssignableFrom(type)) {
        try {
          // 此处的枚举字典必须是枚举类型
          Method enumValuesMethod = ReflectUtil.getMethod(type, "values");
          EnumEntry<?>[] values = ReflectUtil.invokeStatic(enumValuesMethod);
          if (ArrayUtil.isNotEmpty(values)) {
            List<String> list =
                CollectionUtil.toList(values).stream()
                    .map(EnumEntry::getLabel)
                    .collect(Collectors.toList());
            dropdownValues.put(index, list);
          }
        } catch (Exception e) {
          if (log.isDebugEnabled()) {
            log.debug("get EnumEntry values error, {}", e.getMessage());
          }
        }
      } else if (EnumUtil.isEnum(type)) {
        dropdownValues.put(index, EnumUtil.getNames((Class<? extends Enum<?>>) type));
      } else if (boolean.class.isAssignableFrom(type) || Boolean.class.isAssignableFrom(type)) {
        String[] names = columns.get(index).getName();
        List<String> values = this.getBooleanDropdownConstantValues(names, booleanValues);
        dropdownValues.put(index, values);
      }
    }

    if (dropdownValues.isEmpty()) {
      return;
    } else {
      this.registerWriteHandler(new CellDropdownHandler(dropdownValues));
    }
  }

  private List<String> getBooleanDropdownConstantValues(
      String[] labels, Map<Boolean, List<String>> booleanValues) {
    int index = ReadWriteConstant.getBooleanValuesIndex(labels, booleanValues);
    // 以下两个List长度肯定是相等，否则为非法数据
    List<String> tValues = booleanValues.get(true), fValues = booleanValues.get(false);

    return CollectionUtil.toList(tValues.get(index), fValues.get(index));
  }

  protected void setCellHeaderComments() {
    Map<Integer, Column> headerMap = this.getHeaderMap();
    List<Comment> comments = new ArrayList<>(headerMap.size());
    List<Integer> keys = new ArrayList<>(headerMap.keySet());
    for (int index : keys) {
      comments.add(this.getDefaultHeaderComment(headerMap.get(index), index));
    }
    if (!comments.isEmpty()) {
      CellHeaderCommentHandler handler = new CellHeaderCommentHandler(comments);
      this.registerWriteHandler(handler);
    }
  }

  private Comment getDefaultHeaderComment(Column column, int cellIndex) {
    /**
     * 若将字段写入批注则能更好的调试，需要支持配置
     *
     * <pre>
     *   StringBuffer sb = new StringBuffer(column.getKey());
     * </pre>
     */
    StringBuilder sb = new StringBuilder();
    // TODO 需要支持
    // Field field = column.getOriginal();
    // if (field != null) {
    //   ColumnComment columnComment = field.getAnnotation(ColumnComment.class);
    //   if (columnComment != null) {
    //     String describe = columnComment.describe();
    //     if (StrUtil.isNotBlank(describe)) {
    //       if (sb.length() > 0) {
    //         sb.append("\n");
    //       }
    //       sb.append(describe);
    //     }
    //   }
    // }

    return new Comment(0, cellIndex, sb.toString());
  }

  public void setSheetWaterMark(WaterMark setting) {
    if (setting != null && StrUtil.isNotBlank(setting.getContent())) {
      this.registerWriteHandler(new SheetWaterMarkHandler(setting));
    }
  }

  public void setCellLink(Link setting) {
    if (setting != null) {
      this.registerWriteHandler(new CellLinkHandler(setting, columns));
    }
  }

  protected void setCellStyle() {
    this.registerWriteHandler(new CellStyleHandler());
  }

  public void setFreezePaneHandler(FreePane setting) {
    if (setting != null) {
      this.registerWriteHandler(new SheetFreezePaneHandler(setting));
    }
  }
}
